import java.io.*;
import java.math.BigInteger;
import java.net.*;

import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;

/**
 * Socket Server Class
 * 
 * @author Ronald Brown
 * 
 */
public class Server {
	static Socket clientSocket = null;
	static ServerSocket serverSocket = null;
	//Constant for number of clients
	static int NUM_CLIENTS = 10;

	//The array of threads that store all of the clients.
	static clientThread t[] = new clientThread[NUM_CLIENTS];
	//The array of Encryption classes which encrypt and decrypt per client.
	static Encryption e[] = new Encryption[NUM_CLIENTS];

	public static void main(String args[]) {

		// The default port
		int port_number = 2323;

		//Get the port number from the command line if available.
		if (args.length < 1) {
			System.out.println("Usage: java Server \n"
					+ "Now using port number=" + port_number);
		} else {
			port_number = Integer.valueOf(args[0]).intValue();
		}

		//Create the socket.
		try {
			serverSocket = new ServerSocket(port_number);
		} catch (IOException e) {
			System.out.println(e);
		}
		
		//The main server, accepts server connections, and starts threads.
		while (true) {
			try {
				clientSocket = serverSocket.accept();
				for (int i = 0; i < NUM_CLIENTS; i++) {
					if (t[i] == null) {
						(t[i] = new clientThread(clientSocket, t, e)).start();
						break;
					}
				}
			} catch (IOException e) {
				System.out.println(e);
			}
		}
	}
}

//This is the individual thread per client.
class clientThread extends Thread {
	//Initialize all of the variables.
	BufferedReader in = null;
	PrintWriter out = null;
	DataOutputStream dOS = null;
	DataInputStream dIS = null;
	CipherOutputStream cOS = null;
	Socket clientSocket = null;
	clientThread t[];
	DiffieHellman dh;
	int privateKey;
	byte[] publicKey;
	byte[] serverKey;
	SecretKey mainKey;
	Encryption e[];
	String name;
	int id = -1;
	static int NUM_CLIENTS = 10;

	public clientThread(Socket clientSocket, clientThread[] t, Encryption[] e) {
		this.clientSocket = clientSocket;
		this.t = t;
		this.e = e;
	}
	
	public void run() {
		String line;
		String password;
		byte[] apub = null;
		byte[] bpub;
		
		// Set the Thread's ID
		for (int i = 0; i < NUM_CLIENTS; i++)
			if (t[i] == this)
				id = i;

		try {
			//Initialize the diffie hellman class.
			dh = new DiffieHellman();
			//Initialize the streams.
			in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			out = new PrintWriter(clientSocket.getOutputStream(), true);
			dOS = new DataOutputStream(clientSocket.getOutputStream());
			dIS = new DataInputStream(clientSocket.getInputStream());

			//Start Diffie Hellman Key Exchange.
			
			//Create a container for p and g
			BigInteger[] pg = new BigInteger[2];

			//This stores how many bytes to read for the clients public key.
			int bytsToRead = 0;
			
			synchronized (clientSocket) {
				//Read p from the client.
				pg[0] = new BigInteger(in.readLine());
			}
			synchronized (clientSocket) {
				//Read g from the client.
				pg[1] = new BigInteger(in.readLine());
			}
			synchronized (clientSocket) {
				//Read the number of bytes for clients public key from client.
				bytsToRead = Integer.parseInt(in.readLine());
			}

			//This is the clients public key.
			apub = new byte[bytsToRead];
			System.out.println("Byte Length: " + apub.length);

			synchronized (clientSocket) {
				//Read the public key from the client.
				dIS.read(apub, 0, apub.length);
			}
			
			//Create the public key for the server.
			bpub = dh.CreateSendInteger(pg[0], pg[1], 16);

			//Output the two public keys for server information.
			System.out.println("Apub: " + apub + "\nBpub: " + bpub);
			
			synchronized (clientSocket) {
				//Send the number of bytes for the servers public key
				dOS.writeInt(bpub.length);
			}

			synchronized (clientSocket) {
				//Send the servers public key
				dOS.write(bpub);
			}
			
			//Create the secret key with the client.
			SecretKey Kb = dh.CreateFullKey(apub);

			//Create the specific client's encryption class.
			e[id] = new Encryption(Kb);
			
			// Diffie Hellman ends here

			//Ask the client for the username.
			EncComm.send(out, e[id], "Enter your name.");
			//Read in the username.
			name = EncComm.recieve(in, e[id]);
			//Ask the client for the password.
			EncComm.send(out, e[id], "Enter your password.");
			//Read in the username.
			password = EncComm.recieve(in, e[id]);

			//End this thread if the username is in user in another thread.
//			for (int i = 0; i < NUM_CLIENTS; i++)
//				if (t[i] != null && t[i] != this && t[i].name == name)
//					return;
			
			//Authenticate the username and password.
			//boolean auth = Authentication.verify(name, password);
			
			//Sleep for the client.
			try {
				Thread.sleep(1000l);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			//Continue if authorized.
			
				//Welcome the user.
//				String welcome = e[id].encrypt("Hello " + name + " to our chat room. To leave enter /quit in a new line");
//				EncComm.send(out, e[id], "Hello " + name + " to our chat room. To leave enter /quit in a new line");
//
//				// Make sure that each active thread has an encryption.
//				for (int i = 0; i < NUM_CLIENTS; i++) {
//					if (t[i] != null && e[i] == null) {
//						t[i] = null;
//						System.out.println("The following thread was terminated(casually): "+ i);
//					}
//				}
//				
//				// Tell all other clients this user has entered.
//				for (int i = 0; i < NUM_CLIENTS; i++){
//					if (t[i] != null && t[i] != this) {
//						EncComm.send(t[i].out, e[i], name + " has entered the room");
//					}
//				}
				
				//The main loop for this client thread.
				while (true) {
					//Get from the specific client.
					line = EncComm.recieve(in, e[id]);
					//Check for quit.
					if (line.startsWith("/quit"))
						break;
					//Send it to all clients.
					for (int i = 0; i < NUM_CLIENTS; i++) {
						if (t[i] != null) {
							EncComm.send(t[i].out, e[i], "<" + name + ">: "+ line);
						}
					}
				}
				
				//Tell all other clients this client is exiting.
				for (int i = 0; i < NUM_CLIENTS; i++)
					if (t[i] != null && t[i] != this)
						EncComm.send(t[i].out, e[i], "*** The user " + name+ " is leaving the chat room !!! ***");

				//Say bye to this client.
				EncComm.send(out, e[id], "*** Bye " + name + " ***");

				// Clean up:
				t[id] = null;
				e[id] = null;

				//Close the streams.
				in.close();
				out.close();
				//Close the socket/
				clientSocket.close();
			
		} catch (IOException e) {
		}
		;
	}
}